<?php
declare(strict_types=1);

namespace Dreamcat\ApolloPhp\Impl;

use Components\Utils\Funcs\FileSystemHelper;
use Dreamcat\ApolloPhp\Api\ApolloServerInterface;
use Dreamcat\ApolloPhp\ApolloReadProcessInterface;
use Dreamcat\ApolloPhp\ConfigSaverInterface;
use Dreamcat\ApolloPhp\Popo\Enum\QueryStatus;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

/**
 * apollo配置进程
 * @author vijay
 */
class ApolloReadProcess implements ApolloReadProcessInterface
{
    /** @var ApolloServerInterface apollo服务器 */
    private $apolloServer;
    /** @var ConfigSaverInterface 配置保存器 */
    private $configSaver;
    /** @var string 缓存文件名 */
    private $cacheFile;
    /**
     * @var array 各种名空间的缓存内容，元素结构如下{
     *      nid : 上次的通知id
     *      releaseKey : 上次的releaseKey
     * }
     */
    private $cache;
    /** @var LoggerInterface 日志记录器 */
    private $logger;
    /** @var int 拉配置失败的重试次数 */
    private $tryCount = 3;

    /**
     * @return int 拉配置失败的重试次数
     */
    public function getTryCount(): int
    {
        return $this->tryCount;
    }

    /**
     * @param int $tryCount 拉配置失败的重试次数
     * @return static 对象本身
     */
    public function setTryCount(int $tryCount): ApolloReadProcess
    {
        if ($tryCount >= 0) {
            $this->tryCount = $tryCount;
        }
        return $this;
    }

    /**
     * @return LoggerInterface 日志记录器
     */
    public function getLogger(): LoggerInterface
    {
        if (!$this->logger) {
            $this->logger = new NullLogger();
        }
        return $this->logger;
    }

    /**
     * @param LoggerInterface $logger 日志记录器
     * @return static 对象本身
     */
    public function setLogger(LoggerInterface $logger): ApolloReadProcess
    {
        $this->logger = $logger;
        return $this;
    }

    /**
     * ApolloReadProcess constructor.
     * @param string $cacheFile 读取配置的缓存文件，要确保有读写权限
     */
    public function __construct(string $cacheFile)
    {
        if (!FileSystemHelper::touchFile($cacheFile)) {
            throw new \RuntimeException("缓存文件{$cacheFile}无法创建");
        }
        $this->cacheFile = realpath($cacheFile);
        $cache = file_get_contents($this->cacheFile);
        if (strlen($cache)) {
            $this->cache = @unserialize($cache);
        }
        if (!$this->cache) {
            $this->cache = [];
        }
    }

    /**
     * @inheritDoc
     */
    public function setApolloServer(ApolloServerInterface $apolloServer): ApolloReadProcessInterface
    {
        $this->apolloServer = $apolloServer;
        return $this;
    }

    /**
     * @inheritDoc
     */
    public function setConfigSaver(ConfigSaverInterface $configSaver): ApolloReadProcessInterface
    {
        $this->configSaver = $configSaver;
        return $this;
    }

    /**
     * 将缓存内容存入缓存文件
     * @return void
     */
    protected function saveCache(): void
    {
        file_put_contents($this->cacheFile, serialize($this->cache));
    }

    /**
     * @inheritDoc
     */
    public function readConfig(
        string $appId,
        string $clusterName,
        array $namespaces,
        string $ip = null
    ): void {
        if (!$namespaces) {
            $this->getLogger()->warning("传入的名空间列表为空");
            return;
        }
        $notifications = [];
        foreach ($namespaces as $namesapce) {
            if (!isset($this->cache[$namesapce])) {
                $this->cache[$namesapce] = [
                    "nid" => 0,
                    "releaseKey" => null,
                ];
            }
            $notifications[$namesapce] = $this->cache[$namesapce]["nid"];
        }
        while (true) {
            $getNotifica = $this->apolloServer->notifications($appId, $clusterName, $notifications);
            if (!$getNotifica) {
                continue;
            }
            $this->getLogger()->info("{$appId}::{$clusterName}的配置发生了变化");
            foreach ($getNotifica as $namespace => $nid) {
                $tryCnt = 0;
                do {
                    ++$tryCnt;
                    $result = $this->apolloServer->getConfig($appId, $clusterName, $namespace, $ip, $this->cache[$namespace]["releaseKey"]);
                } while ($tryCnt < $this->tryCount && $result->getQueryStatus()->getValue() == QueryStatus::FAILED);
                if ($result->getQueryStatus()->getValue() == QueryStatus::FAILED) {
                    $this->getLogger()->warning("{$appId}::{$clusterName}::{$namespace}的配置拉取失败");
                    continue;
                }
                $this->cache[$namespace]["nid"] = $nid;
                $notifications[$namespace] = $this->cache[$namespace]["nid"];
                if ($result->getQueryStatus()->getValue() != QueryStatus::CHANGED) {
                    continue;
                }
                $this->logger->info("{$appId}::{$clusterName}::{$namespace}的配置拉取成功，准备更新");
                if ($this->configSaver->saveConfig($result)) {
                    $this->logger->info("{$appId}::{$clusterName}::{$namespace}的配置成功更新");
                } else {
                    $this->logger->warning("{$appId}::{$clusterName}::{$namespace}的配置更新失败！");
                }
            }
            $this->configSaver->onSaveAll();
            $this->saveCache();
        }
    }
}

# end of file
